package com.aznest.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.aznest.context.BaseContext;
import com.aznest.dto.RelateDTO;
import com.aznest.entity.*;
import com.aznest.enums.ResultCodeEnum;
import com.aznest.enums.RoleEnum;
import com.aznest.enums.StatusEnum;
import com.aznest.exception.CustomException;
import com.aznest.mapper.*;
import com.aznest.service.HotelService;
import com.aznest.utils.TokenUtils;
import com.aznest.utils.UserCF;
import com.aznest.vo.HotelVO;
import com.aznest.vo.OrdersVO;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class HotelServiceImpl implements HotelService {

    @Resource
    private HotelMapper hotelMapper;
    @Resource
    private CommentMapper commentMapper;
    @Resource
    private TypeMapper typeMapper;
    @Resource
    private OrdersMapper ordersMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private CollectMapper collectMapper;

    @Resource
    private BloomFilterService bloomFilterService;
    private RBloomFilter<Integer> hotelBloomFilter;

    @PostConstruct
    public void initBloomFilter() {
        log.info("初始化布隆过滤器");
        hotelBloomFilter = bloomFilterService.initBloomFilter("hotelBloomFilter", 10000, 0.01);
        List<Integer> hotelIds = hotelMapper.selectIds();
        // 预先将所有酒店id放入布隆过滤器中
        hotelIds.forEach(id -> hotelBloomFilter.add(id));
    }

    @CacheEvict(value = "hotelCache", allEntries = true)
    public void add(Hotel hotel) {
        // 1. 做一下重复性校验
        Hotel hotelUser = hotelMapper.selectByUsername(hotel.getUsername());
        if (ObjectUtil.isNotEmpty(hotelUser)) {
            throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR);

        }
        if (ObjectUtil.isEmpty(hotel.getPassword())) {
            hotel.setPassword("123456");
        }
        if (ObjectUtil.isEmpty(hotel.getRole())) {
            hotel.setRole(RoleEnum.HOTEL.name());
        }
        if (ObjectUtil.isEmpty(hotel.getAvatar())) {
            hotel.setAvatar("http://localhost:9091/files/1697438073596-avatar.png");
        }
        hotel.setStatus(StatusEnum.CHECKING.status);
        hotelMapper.insert(hotel);
        hotelBloomFilter.add(hotel.getId());
    }

    public PageInfo<Hotel> selectPage(Hotel hotel, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<Hotel> hotels = hotelMapper.selectAll(hotel);
        return PageInfo.of(hotels);
    }

    @CacheEvict(value = "hotelCache", allEntries = true)
    public void update(Hotel hotel) {
        hotelMapper.updateById(hotel);
    }

    @CacheEvict(value = "hotelCache", key = "#id")
    public void deleteById(Integer id) {
        hotelMapper.deleteById(id);
    }

    @CacheEvict(value = "hotelCache", allEntries = true)
    public void deleteBatch(List<Integer> list) {
        for (Integer id : list) {
            deleteById(id);
        }
    }

    public Account login(Account account) {
        Account hotel = hotelMapper.selectByUsername(account.getUsername());
        if (ObjectUtil.isNull(hotel)) {
            throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);
        }
        if (!account.getPassword().equals(hotel.getPassword())) {
            throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR);
        }
        // 生成token
        String tokenData = hotel.getId() + "-" + RoleEnum.HOTEL.name();
        String token = TokenUtils.createToken(tokenData, hotel.getPassword());
        hotel.setToken(token);
        return hotel;
    }

    public void register(Account account) {
        Hotel hotel = new Hotel();
        BeanUtils.copyProperties(account, hotel);
        add(hotel);
    }

    @Cacheable(value = "hotelCache#4566", key = "#id")
    public Hotel selectById(Integer id) {
//         使用布隆过滤器判断是否存在该酒店
        if (!hotelBloomFilter.contains(id)) {
            throw new CustomException(ResultCodeEnum.HOTEL_NOT_EXIST_ERROR);
        }
        Hotel hotel = hotelMapper.selectById(id);
        if (!hotel.getStatus().equals(StatusEnum.CHECK_OK.status)) {
            throw new CustomException(ResultCodeEnum.HOTEL_NOT_EXIST);
        }
        return hotel;
    }

    public void updatePassword(Account account) {
        Hotel dbHotel = hotelMapper.selectByUsername(account.getUsername());
        if (ObjectUtil.isNull(dbHotel)) {
            throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);
        }
        if (!account.getPassword().equals(dbHotel.getPassword())) {
            throw new CustomException(ResultCodeEnum.PARAM_PASSWORD_ERROR);
        }
        dbHotel.setPassword(account.getNewPassword());
        hotelMapper.updateById(dbHotel);
    }
    /**
     * 基于协同过滤算法推荐酒店
     * @return
     */
//    @Cacheable(value = "hotelCache#4566", key = "'recommend'")
    @Override
    public List<HotelVO> recommend() {
        Account currentUser = BaseContext.getCurrentUser();
        // 1. 根据收藏和预订记录推荐
        // 获取所有的User
        List<User> users = userMapper.selectAll(new User());
        // 获取所有的酒店
        List<Hotel> hotels = hotelMapper.selectAll(new Hotel());
        // 获取所有的收藏信息
        List<Collect> collects = collectMapper.selectAll(new Collect());
        // 获取所有的订单信息
        List<OrdersVO> orders = ordersMapper.selectAll(new Orders());
        // 2. 存储所有的用户和酒店
        List<RelateDTO> data = new ArrayList<>();
        // 开始计算所有用户和酒店的相似度
        for (Hotel hotel : hotels) {
            if(!hotel.getStatus().equals(StatusEnum.CHECK_OK.status))
                continue;
            Integer hotelId = hotel.getId();
            for (User user : users) {
                Integer userId = user.getId();
                int index = 1;
                // 如果该用户收藏过该酒店，权重给他1
                Optional<Collect> collectOptional = collects.stream().filter(x -> x.getHotelId().equals(hotelId) && x.getUserId().equals(userId)).findFirst();
                if (collectOptional.isPresent()) {
                    index += 1;
                }

                // 如果该用户预订过该酒店，权重给他2
                Optional<OrdersVO> ordersVOOptional = orders.stream().filter(x -> x.getHotelId().equals(hotelId) && x.getUserId().equals(userId)).findFirst();
                if (ordersVOOptional.isPresent()) {
                    index += 2;
                }

                if(index > 1){
                    RelateDTO relate = new RelateDTO(userId, hotelId, index);
                    data.add(relate);
                }
            }
        }
        List<Integer> recommendHotelIds = UserCF.recommend(currentUser.getId(), data);
        // 如果沒有推薦的酒店，随机返回4个
        if(CollUtil.isEmpty(recommendHotelIds)){
            List<HotelVO> hotelVOS = selectAll(new Hotel());
            Collections.shuffle(hotelVOS);
            List<HotelVO> result = hotelVOS.stream().limit(4).collect(Collectors.toList());
            return result;
        }

        List<Hotel> result = hotels.stream().filter(x -> recommendHotelIds.contains(x.getId())).collect(Collectors.toList());
        Collections.shuffle(result);
        List<HotelVO> hotelVOList = new ArrayList<>();
        wrapHotelVO(result, hotelVOList);
        if (hotelVOList.size() > 4 ) {
            return hotelVOList.subList(0, 4);
        }
        return hotelVOList;
    }

    /**
     * 热点酒店
     * @return
     */
//    @Cacheable(value = "hotelCache#4566", key = "'hot'")
    @Override
    public List<HotelVO> selectHot() {
        List<HotelVO> hotelVOS = selectAll(new Hotel());
        // 从评论数改为预定数
        List<HotelVO> result = hotelVOS.stream().sorted(Comparator.comparing(HotelVO::getBookCount).reversed()).limit(4).collect(Collectors.toList());
        return result;
    }

    /**
     * 查询所有酒店信息
     * @param hotel
     * @return
     */
    @Cacheable(value = "hotelCache",key = "'all'")
    public List<HotelVO> selectAll(Hotel hotel) {
        List<Hotel> hotels = hotelMapper.selectAll(hotel);
        List<HotelVO> hotelVOList = new ArrayList<>(hotels.size());
        wrapHotelVO(hotels, hotelVOList);
        return hotelVOList;
    }


    public List<HotelVO> selectByName(String name) {
        Hotel hotel = new Hotel();
        if (ObjectUtil.isNotEmpty(name) && !"null".equals(name)) {
            hotel.setName(name);
        }else{
            throw new CustomException(ResultCodeEnum.HOTEL_SEARCH_PARAM_ERROR);
        }
        List<Hotel> hotels = hotelMapper.selectAll(hotel);
        List<HotelVO> hotelVOList = new ArrayList<>(hotels.size());
        wrapHotelVO(hotels, hotelVOList);
        return hotelVOList;
    }

    public List<Hotel> selectAllOK() {
        return hotelMapper.selectAllOK();
    }

    /**
     * 封裝HotelVO对象所需信息
     * @param hotels
     * @param hotelVOList
     */
    private void wrapHotelVO(List<Hotel> hotels, List<HotelVO> hotelVOList) {
        for (Hotel h : hotels) {
            if(!h.getStatus().equals(StatusEnum.CHECK_OK.status))
                continue;
            HotelVO hotelVO = new HotelVO();
            BeanUtils.copyProperties(h, hotelVO);
            List<Comment> comments = commentMapper.selectByHotelId(h.getId());
            hotelVO.setCommentCount(comments.size());
            // 找出酒店最低价格
            List<Type> typeList = typeMapper.selectByHotelId(h.getId());
            // 从typeList中找到最低价格
            Double minPrice = Double.MAX_VALUE;
            for (Type type : typeList) {
                minPrice = Math.min(minPrice, type.getPrice());
            }
            // 设置酒店最低价格
            if (minPrice == Double.MAX_VALUE) {
                hotelVO.setPrice(0d);
            }else{
                hotelVO.setPrice(minPrice);
            }
            // 找出该酒店预订数
            List<Orders> ordersList = ordersMapper.selectByHotelId(h.getId());
            hotelVO.setBookCount(ordersList.size());

            // 添加当前酒店到列表中
            hotelVOList.add(hotelVO);
        }
    }
}
